package org.light.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.light.complexverb.AddUploadDomainField;
import org.light.complexverb.Assign;
import org.light.complexverb.ListMyActive;
import org.light.complexverb.ListMyAvailableActive;
import org.light.complexverb.Revoke;
import org.light.complexverb.TwoDomainVerb;
import org.light.domain.Controller;
import org.light.domain.Domain;
import org.light.domain.Dropdown;
import org.light.domain.Field;
import org.light.domain.ManyToMany;
import org.light.domain.Method;
import org.light.domain.Statement;
import org.light.domain.StatementList;
import org.light.domain.ValidateInfo;
import org.light.exception.ValidateException;
import org.light.generator.NamedUtilMethodGenerator;
import org.light.limitedverb.CountSearchByFieldsRecords;
import org.light.simpleauth.verb.LoginUser;
import org.light.simpleauth.verb.LogoutUser;
import org.light.simpleauth.verb.RegisterUser;
import org.light.utils.DomainUtil;
import org.light.utils.StringUtil;
import org.light.utils.WriteableUtil;
import org.light.verb.CheckAccess;
import org.light.verb.Export;
import org.light.verb.ExportPDF;
import org.light.verb.FilterExcel;
import org.light.verb.FilterPDF;
import org.light.verb.SearchByFields;

public class AxumController extends Controller implements Comparable<Controller>{
	private static final long serialVersionUID = 6506582842158817100L;
	protected List<Verb> verbs = new ArrayList<Verb>();
	protected Set<ManyToMany> mtms = new TreeSet<>();
	protected List<TwoDomainVerb> twoDomainVerbs = new ArrayList<>();
	protected String crossOrigin = "";
	protected Domain domain;

	public Set<ManyToMany> getMtms() {
		return mtms;
	}

	public void setMtms(Set<ManyToMany> mtms) {
		this.mtms = mtms;
	}

	public List<TwoDomainVerb> getTwoDomainVerbs() {
		return twoDomainVerbs;
	}

	public void setTwoDomainVerbs(List<TwoDomainVerb> twoDomainVerbs) {
		this.twoDomainVerbs = twoDomainVerbs;
	}
	
	public void addTwoDomainVerb(TwoDomainVerb twoDomainVerb) {
		this.twoDomainVerbs.add(twoDomainVerb);
	}

	public void setDomain(Domain domain) {
		this.domain = domain;
	}

	public List<Verb> getVerbs() {
		return verbs;
	}

	public void setVerbs(List<Verb> verbs) {
		this.verbs = verbs;
	}

	public void addVerb(Verb verb) {
		this.verbs.add(verb);
	}

	public AxumController(Domain domain) throws ValidateException {
		super();
		this.domain = domain;
	}

	@Override
	public String generateControllerString() throws ValidateException {
		StringBuilder sb = new StringBuilder();

		Set<String> imports = this.generateImportStrings();
		imports.add("axum::{\r\n"
				+ "    extract::{Form, Path, Query, Json, Multipart,DefaultBodyLimit},\r\n"
				+ "    http:: {HeaderMap, HeaderValue,StatusCode},\r\n"
				+ "    routing::{get_service,get,post,MethodRouter},\r\n"
				+ "    response::{IntoResponse, Response},\r\n"
				+ "    body::{Full, Bytes},\r\n"
				+ "    Router\r\n"
				+ "}");
		imports.add("serde_json::{Value,Map}");	
		imports.add("serde::{Deserialize}");	
		imports.add("tower_http::{trace::TraceLayer}");
				
		imports.add(this.domain.getProjectName()+"::"+this.domain.getDomainSuffix()+"::"+this.domain.getCapFirstDomainNameWithSuffix());
		imports.add(this.domain.getProjectName()+"::"+this.domain.getDomainSuffix()+"::"+this.domain.getCapFirstDomainName()+"QueryRequest");
		
		for (Verb v:this.getVerbs()) {
			try {
				if (v.generateServiceImplMethod()!=null) {
					if (!v.isDenied() && ! (v instanceof Export|| v instanceof FilterExcel)&& ! (v instanceof ExportPDF|| v instanceof FilterPDF || v instanceof LoginUser)) {
						imports.add("crate::"+this.domain.getServiceimplSuffix()+"::"+v.getDomain().getSnakeDomainName()+"_service"+"::"+StringUtil.getSnakeName(v.getVerbName())+" as service_"+StringUtil.getSnakeName(v.getVerbName()));
					}
				}
			} catch (Exception e) {
				throw new ValidateException(e.getMessage());
			}
		}
		
		if (this.domain.containsImage()) {
			imports.add("std::sync::Mutex");
			imports.add("std::collections::HashMap");
		}
		CountSearchByFieldsRecords countSearch = new CountSearchByFieldsRecords(this.domain);
		imports.add("crate::"+this.domain.getServiceimplSuffix()+"::"+this.domain.getSnakeDomainName()+"_service"+"::"+StringUtil.getSnakeName(countSearch.getVerbName())+" as service_"+StringUtil.getSnakeName(countSearch.getVerbName()));
		if (this.twoDomainVerbs!=null && this.twoDomainVerbs.size()>0) {
			for (TwoDomainVerb tdv:this.getTwoDomainVerbs()) {
				imports.add("crate::"+this.domain.getServiceimplSuffix()+"::"+tdv.getMaster().getSnakeDomainName()+"_service"+"::"+StringUtil.getSnakeName(tdv.getVerbName())+" as service_"+StringUtil.getSnakeName(tdv.getVerbName()));
			}
		}

		imports.addAll(this.classImports);
		String importsStr = DomainUtil.generateImportStr(imports);
		sb.append(importsStr);
		sb.append("\n");
		
		if (this.domain.containsImage()) {
			sb.append(NamedUtilMethodGenerator.generateImageLazyStaticMac(this.domain).generateMethodString()).append("\n");
			sb.append(NamedUtilMethodGenerator.generateStoreImageFunc(this.domain).generateMethodString()).append("\n");
			sb.append(NamedUtilMethodGenerator.generatePickImageFunc(this.domain).generateMethodString()).append("\n");
		}
		
		sb.append(getRequestsStatementList().getContent());
		sb.append(getControllerRoutesStatementList().getContent());

		Iterator it2 = this.getMethods().iterator();
		while (it2.hasNext()) {
			sb.append(((Method) it2.next()).generateMethodString()).append("\n");
		}
		return sb.toString();
	}
	
	public StatementList getRequestsStatementList() {
		List<Writeable> sList = new ArrayList<Writeable>();
		long serial = 1000L;
		sList.add(new Statement(serial+1000L,0,"#[derive(Deserialize)]"));
		sList.add(new Statement(serial+2000L,0,"#[serde(rename_all = \"camelCase\")]"));
		sList.add(new Statement(serial+3000L,0,"pub struct "+this.domain.getCapFirstDomainName()+"Request {"));
		serial += 4000L;
		for (Field f:this.domain.getFields()) {
			if (!"Image".equalsIgnoreCase(f.getFieldType())) {
				sList.add(new Statement(serial,1,"pub "+f.getSnakeFieldName()+": Option<"+f.getFieldType()+">,"));
				serial += 1000L;
			}						
		}
		sList.add(new Statement(serial,0,"}"));
		sList.add(new Statement(serial+500L,0,""));
		sList.add(new Statement(serial+1000L,0,"#[derive(Deserialize)]"));
		sList.add(new Statement(serial+2000L,0,"#[serde(rename_all = \"camelCase\")]"));
		sList.add(new Statement(serial+3000L,0,"pub struct IdsRequest {"));
		sList.add(new Statement(serial+4000L,1,"pub ids: String,"));
		sList.add(new Statement(serial+5000L,0,"}"));
		serial += 6000L;
		if (this.mtms!=null && this.mtms.size()>0) {
			for (ManyToMany mtm:this.mtms) {
				sList.add(new Statement(serial,0,"#[derive(Deserialize)]"));
				sList.add(new Statement(serial+1000L,0,"#[serde(rename_all = \"camelCase\")]"));
				sList.add(new Statement(serial+2000L,0,"pub struct "+mtm.getMaster().getCapFirstDomainName()+mtm.getSlaveAlias()+"MtmRequest {"));
				sList.add(new Statement(serial+3000L,1,"pub "+mtm.getMaster().getSnakeDomainName()+"_id: Option<i64>,"));
				sList.add(new Statement(serial+4000L,1,"pub "+StringUtil.getSnakeName(mtm.getSlaveAlias())+"_ids:Option<String>,"));
				sList.add(new Statement(serial+5000L,0,"}"));
				sList.add(new Statement(serial+6000L,0,""));
				serial += 7000L;
			}
		}
		return WriteableUtil.merge(sList);
	}
	
	public StatementList getControllerRoutesStatementList() throws ValidateException {
		List<Writeable> sList = new ArrayList<Writeable>();
		long serial = 1000L;
		sList.add(new Statement(serial,0,""));
		sList.add(new Statement(serial+1000L,0,"pub(crate) fn "+this.domain.getSnakeDomainName()+"_"+StringUtil.getSnakeName(this.domain.getControllerSuffix())+"() -> Router {"));
		sList.add(new Statement(serial+2000L,1,"Router::new()"));
		serial += 3000L;
		for (Verb v : this.verbs) {
			try {
				if (!v.isDenied()&& !(v instanceof SearchByFields) && v.generateControllerMethod()!=null && ! (v instanceof RegisterUser || v instanceof LoginUser || v instanceof LogoutUser)) {
					if (v instanceof Export ||v instanceof FilterExcel ||v instanceof ExportPDF ||v instanceof FilterPDF || v instanceof CheckAccess) {
						sList.add(new Statement(serial,1,".route(\"/"+StringUtil.lowerFirst(v.getVerbName())+"\", get("+StringUtil.getSnakeName(v.getVerbName())+"))"));
					}else {
						sList.add(new Statement(serial,1,".route(\"/"+StringUtil.lowerFirst(v.getVerbName())+"\", post("+StringUtil.getSnakeName(v.getVerbName())+"))"));
					}
					serial += 1000L;
				}
			} catch (Exception e) {
				throw new ValidateException(e.getMessage());
			}
		}
		
		if (this.twoDomainVerbs!=null && this.twoDomainVerbs.size()>0) {
			for (TwoDomainVerb tdv:this.getTwoDomainVerbs()) {
				sList.add(new Statement(serial,1,".route(\"/"+StringUtil.lowerFirst(tdv.getVerbName())+"\", post("+StringUtil.getSnakeName(tdv.getVerbName())+"))"));
				serial += 1000L;
			}
		}
		if (this.domain.containsImage()) {
			for (Field f:this.domain.getPlainFields()) {
				if ("Image".equalsIgnoreCase(f.getFieldType())) {
					AddUploadDomainField au = new AddUploadDomainField(this.domain,f);
					sList.add(new Statement(serial,1,".route(\"/"+StringUtil.lowerFirst(au.getVerbName())+"\", post("+StringUtil.getSnakeName(au.getVerbName())+"))"));
					serial += 1000L;
				}
			}
		}
		sList.add(new Statement(serial+17000L,1,".layer(DefaultBodyLimit::max(2*1024*1024))"));
		sList.add(new Statement(serial+18000L,1,".layer(TraceLayer::new_for_http())"));
		sList.add(new Statement(serial+20000L,0,"}"));
		return WriteableUtil.merge(sList);
	}

	public ValidateInfo validate() {
		ValidateInfo info = new ValidateInfo();
		info.setSuccess(true);
		if (this.verbs == null || this.verbs.size() == 0) {
			info.setSuccess(false);
			info.addCompileError("Controller " + this.standardName
					+ " 动词是空！");
		}
		for (Verb v : this.verbs) {
			if (v != null && v.getDomain() == null) {
				info.setSuccess(false);
				info.addCompileError("Controller " + this.standardName + "动词"
						+ v.getVerbName() + "域对象是空值！");
			}
		}
		return info;
	}

	public AxumController(List<Verb> verbs, Domain domain,Boolean ignoreWarning, Set<ManyToMany> mtms)
			throws ValidateException {
		super();
		this.domain = domain;
		this.mtms = mtms;
		
		for (ManyToMany mtm:this.mtms) {
			Domain master = mtm.getMaster();
			Domain slave = mtm.getSlave();
			if (slave != null) {
				if (!StringUtil.isBlank(mtm.getSlaveAlias())&& slave != null) slave.setAlias(mtm.getSlaveAlias());
				this.twoDomainVerbs.add(new Assign(master,slave));
				this.twoDomainVerbs.add(new Revoke(master,slave));
				this.twoDomainVerbs.add(new ListMyActive(master,slave));
				this.twoDomainVerbs.add(new ListMyAvailableActive(master,slave));
			}
		}
		try{
			for (Verb v : verbs) {
				v.setDomain(domain);
			}
			this.verbs = verbs;
			this.standardName = domain.getCapFirstDomainName() + domain.getControllerNamingSuffix();
	
			ValidateInfo info = this.validate();
			if (info.success(ignoreWarning)) {
				for (Field f:domain.getFields()) {
					if (f instanceof Dropdown) {
						Dropdown dp = (Dropdown)f;
					}
				}
			} else {
				ValidateException e = new ValidateException(info);
				throw e;
			}
		}catch (Exception ex){
			ValidateInfo info = new ValidateInfo();
			info.addCompileError("Gin控制器新建失败！");
			ValidateException e = new ValidateException(info);
			throw e;
		}
	}

	public String getCapFirstControllerName() {
		return StringUtil.capFirst(this.getStandardName());
	}

	public String getLowerFirstControllerName() {
		return StringUtil.lowerFirst(this.getStandardName());
	}

	public Domain getDomain() {
		if (this.verbs != null && this.verbs.size() > 0)
			return this.verbs.get(0).getDomain();
		else if (this.domain!=null) {
			return this.domain;
		}
		else {
			return null;
		}
	}

	public Method findVerbMethodByName(String methodname) throws Exception {
		return null;
	}

	@Override
	public int compareTo(Controller o) {
		return this.standardName.compareTo(o.getStandardName());
	}

	public String getCrossOrigin() {
		return crossOrigin;
	}

	public void setCrossOrigin(String crossOrigin) {
		this.crossOrigin = crossOrigin;
	}

}
